#solution 1:
#use following estimation
#bluetooth send/receive: 1.6J (whatever data size. i.e. data size is small)
#3G transfer (new connection): 11.5J
#3G transfer (with voice call): 1.2J

import operator
import pymysql as db
import sqlite3
from pprint import pprint
from datetime import datetime
from datetime import timedelta

#conn = db.connect(host='127.0.0.1', port=3306, user='root', passwd='wlywly', db='mit_reality_full')
conn = sqlite3.connect('E:/dataset research/nodobo-release/db/nodobo.db')

def execute_query_fetch_all(query):
    cur = conn.cursor()
    cur.execute(query) 
    results = cur.fetchall() 
    cur.close()
    return results

#TEST_DATE = datetime(2004, 11, 8, 0, 0, 0)
TEST_DATE = datetime(2010, 11, 8, 0, 0, 0)

TIME_SPLIT = 60

#nodobo settings
DATA_USERS_ID = [1,2,3,4,5,6,7,8,9,10,11,12,13]
NON_DATA_USERS_ID = [14,15,16,17,18,19,20,21,22,23,24,25,26,27]

#mit settings
#DATA_USERS_ID = [29,39,79,37,47,63,91,15,57,80,75,69,94,78,43,95,72,73,16,85,88,60,35,54,50,66,17,18,20,28]
#DATA_USER_DEVICES_ID = [15,16,17,49,18,40,20,29,30,36,38,41,93,46,51,54,58,61,64,67,70,73,76,77,79,83,84,85,90,94,97,100,101]
#
#NON_DATA_USERS_ID = [1,2,3,4,6,7,8,10,11,12,14,19,22,23,24,26,32,33,36,38,40,42,46,49,51,59,61,65,70,74,76,77,81,83,84,86,89,90,92,96,97]
#NON_DATA_USER_DEVICES_ID = [1,2,3,4,6,7,8,10,11,12,14,19,22,23,24,25,27,33,34,37,39,42,43,45,50,53,55,63,65,69,74,78,80,81,82,86,88,89,91,95,96,98,102,103]

ALL_USERS_ID = DATA_USERS_ID + NON_DATA_USERS_ID
#ALL_USER_DEVICES_ID = DATA_USER_DEVICES_ID + NON_DATA_USER_DEVICES_ID

#FIXED_DEVICES_ID = [120, 438]

total_energy = 0.0
total_energy_data = 0.0

#assumption about the energy cost
#assumption 1: 3g_new = 12, 3g_voice_call = 3, bluetooth = 0.5
#ass
#energy_3g_new = 12
#energy_3g_voice_call = 3
#energy_bluetooth = 1

result_unit_size = 0

def energy_3g_new(size):
	if size == 0:
		return 12.0
	return 12.0 + 0.025*size

def energy_3g_voice_call(size):
	if size == 0:
		return 3
	return 0.25*energy_3g_new(size)

def energy_bluetooth(size):
	if size == 0:
		return 1
	return 1 + 0.003*size

class CriticalEvent:
	def __init__(self, user_id, event_time, event_type):
		self.user = user_id
		self.time = event_time
		self.type = event_type #0:voice call, 1:meet device, 2:meet other user
		self.user2 = "None"

	def description(self):
		return "{}\t{}\t{}\t{}".format(self.time, self.type, self.user, self.user2)

def is_data_user(user):
	if int(user) in DATA_USERS_ID:
		return True
	else:
		return False


def findAllCriticalEvents(date_query):
	voice_call_events = findAllVoiceCalls(date_query)
	meet_device_events = findAllFixedDeviceMeetings(date_query)
	meet_user_events = findAllUserMeetings(date_query)
	all_events = voice_call_events + meet_device_events + meet_user_events
	#sort according to event happening time
	all_events.sort(key=operator.attrgetter('time'))
	with open("events/all_events_{}.txt".format(date_query.day), "w") as outputfile:
		for event in all_events:
			outputfile.write("{} {} {} {}\n".format(event.time, event.type, event.user, event.user2))
	return all_events

def findAllCriticalEvents_nodobo(date_query):
	voice_call_events = findAllVoiceCalls_nodobo(date_query)
	meet_user_events = findAllUserMeetings_nodobo(date_query)
	all_events = voice_call_events + meet_user_events
	#sort according to event happening time
	all_events.sort(key=operator.attrgetter('time'))
	with open("events/all_events_{}.txt".format(date_query.day), "w") as outputfile:
		for event in all_events:
			outputfile.write("{} {} {} {}\n".format(event.time, event.type, event.user, event.user2))
	return all_events

def findAllVoiceCalls_nodobo(date_query):
	#only useful for data users
	starttime = date_query
	endtime = date_query + timedelta(days=1)
	all_data_users_id = ','.join(str(x) for x in DATA_USERS_ID)
	query = """
			select user_id, call_timestamp from calls
			where user_id in ({data_users})
			and call_timestamp between '{start}' and '{end}'
			and direction <> 'Missed'
			""".format(data_users=all_data_users_id, start=starttime, end=endtime)
	results = execute_query_fetch_all(query)
	events = []
	for result in results:
		new_voice_call = CriticalEvent(result[0], datetime.strptime(result[1],'%Y-%m-%d %H:%M:%S'), 0)
		events.append(new_voice_call)
	return events


def findAllUserMeetings_nodobo(date_query):
	#useful for all users
	starttime = date_query
	endtime = date_query + timedelta(days=1)
	query = """
			select user_id, timestamp, other_id from presences
			where other_id is not null
			and timestamp between '{start}' and '{end}'
			order by timestamp
			""".format(start=starttime, end=endtime)
	results = execute_query_fetch_all(query)
	events = []
	for result in results:
		new_user_meeting = CriticalEvent(result[0], datetime.strptime(result[1],'%Y-%m-%d %H:%M:%S'), 2)
		connect_user = result[2]
		new_user_meeting.user2 = connect_user
		events.append(new_user_meeting)
	return events

def findAllVoiceCalls(date_query):
	#only useful for data users
	starttime = date_query
	endtime = date_query + timedelta(days=1)
	all_data_users_id = ','.join(str(x) for x in DATA_USERS_ID)
	query = """
			select person_oid, starttime from callspan
			where person_oid in ({data_users})
			and starttime between '{start}' and '{end}'
			""".format(data_users=all_data_users_id, start=starttime, end=endtime)
	results = execute_query_fetch_all(query)
	events = []
	for result in results:
		new_voice_call = CriticalEvent(result[0], result[1], 0)
		events.append(new_voice_call)
	return events

def findAllFixedDeviceMeetings(date_query):
	#useful for all users
	starttime = date_query
	endtime = date_query + timedelta(days=1)
	all_users_id = ','.join(str(x) for x in ALL_USERS_ID)
	fixed_devices_id = ','.join(str(x) for x in FIXED_DEVICES_ID)
	query = """
			select person_oid, starttime from devicespan
			where person_oid in ({all_users})
			and starttime between '{start}' and '{end}'
			and device_oid in ({fixed_devices})
			""".format(all_users=all_users_id, start=starttime, end=endtime, fixed_devices=fixed_devices_id)
	results = execute_query_fetch_all(query)
	events = []
	for result in results:
		new_device_meeting = CriticalEvent(result[0], result[1], 1)
		events.append(new_device_meeting)
	return events

def findAllUserMeetings(date_query):
	#useful for all users
	starttime = date_query
	endtime = date_query + timedelta(days=1)
	all_users_id = ','.join(str(x) for x in ALL_USERS_ID)
	user_devices_id = ','.join(str(x) for x in ALL_USER_DEVICES_ID)
	query = """
			select person_oid, starttime, device_oid from devicespan
			where person_oid in ({all_users})
			and starttime between '{start}' and '{end}'
			and device_oid in ({user_devices})
			""".format(all_users=all_users_id, start=starttime, end=endtime, user_devices=user_devices_id)
	results = execute_query_fetch_all(query)
	events = []
	for result in results:
		new_user_meeting = CriticalEvent(result[0], result[1], 2)
		connect_user = device_to_user(result[2])
		new_user_meeting.user2 = connect_user
		events.append(new_user_meeting)
	return events

device2user = {}
def init_device_to_user_dict():
	with open('device_id2user_id.txt','r') as inputfile:
		for line in inputfile.readlines():
			word = line.split()
			device2user[word[0]]=word[1]

def device_to_user(device_id):
	return device2user[str(device_id)]

user_statuses = {}
STATUS_IDLE = 0

def init_user_statues():
	for user in ALL_USERS_ID:
		user_statuses[user] = 1

def status(user_id):
	return user_statuses[int(user_id)]

def set_status(user_id, new_status):
	user_statuses[int(user_id)] = new_status

def solution(date_query, isNodobo = False):
	if isNodobo:
		critical_events = findAllCriticalEvents_nodobo(date_query)
	else:
		critical_events = findAllCriticalEvents(date_query)
	for e in critical_events:
		if (status(e.user) == STATUS_IDLE):
			continue
		if (is_data_user(e.user)):
			#data_user_strategy_always_no(e)
			data_user_strategy_greedy(e)
			#data_user_strategy_expect_based(e)
		else:
			#non_data_user_strategy_greedy(e)
			non_data_user_strategy_expect(e)

	#force_upload_for_data_users()

	num_not_uploaded = 0
	users_not_upload = ""
	num_force_uploaded = 0
	global total_energy, total_energy_data

	for user_id in user_statuses.keys():
		if not is_data_user(user_id) and status(user_id)>0:
			num_not_uploaded += status(user_id)
			users_not_upload += "{},".format(user_id)
		elif status(user_id) > 0:
			num_force_uploaded += 1
			total_energy += energy_3g_new(status(user_id)*result_unit_size)
			total_energy_data += energy_3g_new(status(user_id)*result_unit_size)
			#for continuous test, as one user forced uploading. then the next day he doesn't need to upload anything
			set_status(user_id, 0)
			continue
		#reset all users excpet the force-uploading ones
		set_status(user_id, 1)

	print "{} {} {} {} {}".format(date_query, total_energy, num_not_uploaded, num_force_uploaded, total_energy_data)
	#print users_not_upload


def data_user_strategy_expect_based(event):
	expected_energy_cost = calculate_energy_cost_expect(event.user, event.time, status(event.user))
	current_energy_cost = 100000
	if event.type == 0: #voice call
		current_energy_cost = energy_3g_voice_call(status(event.user)*result_unit_size)
	if event.type == 1: #meet fixed location device
		current_energy_cost = energy_bluetooth(status(event.user)*result_unit_size)
	if event.type == 2 and is_data_user(event.user2): #meet another user
		current_energy_cost = 2 * energy_bluetooth(status(event.user)*result_unit_size) + calculate_energy_cost_expect(event.user2, event.time, status(event.user))

	current_energy_cost += expected_energy_cost * predict_p_receive_other(event.user, event.time)

	if expected_energy_cost >= current_energy_cost:
		transfer_data(event)

def data_user_strategy_always_no(event):
	pass

def data_user_strategy_greedy(event):
	if event.type == 0 or event.type == 1:
		transfer_data(event)

def non_data_user_strategy_greedy(event):
	if event.type == 1:
		transfer_data(event)
	if event.type == 2:
		if is_data_user(event.user2):
			transfer_data(event)

def non_data_user_strategy_expect(event):
	if event.type == 1:
		transfer_data(event)
	elif event.type == 2:
		if is_data_user(event.user2):
			transfer_data(event)
		else:
			p_meet_data_current_user = predict_p_meet_data(event.user, event.time)
			p_meet_data_user2 = predict_p_meet_data(event.user2, event.time)
			if (p_meet_data_current_user < p_meet_data_user2):
				transfer_data(event)


transfer_records_str = ""
bt_user_transfer_records_str = ""

def transfer_data(event):
	global total_energy, total_energy_data
	if event.type == 0:
		total_energy += energy_3g_voice_call(status(event.user)*result_unit_size)
		total_energy_data += energy_3g_voice_call(status(event.user)*result_unit_size)
	if event.type == 1:
		total_energy += energy_bluetooth(status(event.user)*result_unit_size)
		if is_data_user(event.user):
			total_energy_data += energy_bluetooth(status(event.user)*result_unit_size)
	if event.type == 2:
		total_energy += 2*energy_bluetooth(status(event.user)*result_unit_size)
		set_status(event.user2, status(event.user2)+status(event.user))
		if is_data_user(event.user):
			total_energy_data += energy_bluetooth(status(event.user)*result_unit_size)
		if is_data_user(event.user2):
			total_energy_data += energy_bluetooth(status(event.user)*result_unit_size)
	set_status(event.user, STATUS_IDLE)
	#log
	global transfer_records_str, bt_user_transfer_records_str
	transfer_records_str += event.description() + "\n"
	if event.type == 2:
		bt_user_transfer_records_str += event.description() + "\n"

def calculate_energy_cost_expect(user, date_time, result_num):
	if (result_num == 0):
		return 0
	p_voice_call = predict_p_voice_call(user, date_time)
	p_meet_device = predict_p_meet_device(user, date_time)
	s = result_num*result_unit_size
	if (energy_3g_voice_call(s) < energy_bluetooth(s)):
		expect = energy_3g_voice_call(s)*p_voice_call + energy_bluetooth(s)*(1-p_voice_call)*p_meet_device + (1-p_voice_call)*(1-p_meet_device)*energy_3g_new(s)
	else:
		expect = energy_bluetooth(s)*p_meet_device + energy_3g_voice_call(s)*(1-p_meet_device)*p_voice_call + (1-p_voice_call)*(1-p_meet_device)*energy_3g_new(s)
	return expect

p_event_voice_calls = {}
p_event_meet_devices = {}
p_non_data_meet_data = {}
p_data_receive_other = {}

def event_key(user, weekday, time_slot):
	return "{}_{}_{}".format(user, weekday, time_slot)

def init_p_events_mit():
	with open("event_probability.tsv", "r") as inputfile:
		for lines in inputfile.readlines():
			words = lines.split()
			dict_key = event_key(words[0], words[1], words[2])
			p_event_voice_calls[dict_key] = float(words[3])
			p_event_meet_devices[dict_key] = float(words[4])
	with open("non_data_event_probability.tsv", "r") as inputfile:
		for lines in inputfile.readlines():
			words = lines.split()
			dict_key = event_key(words[0], words[1], words[2])
			p_non_data_meet_data[dict_key] = float(words[3])
	with open("data_user_receive_other_probability.tsv", "r") as inputfile:
		for lines in inputfile.readlines():
			words = lines.split()
			dict_key = event_key(words[0], words[1], words[2])
			p_data_receive_other[dict_key] = float(words[3])

def init_p_events_nodobo():
	with open("event_probability_nodobo.tsv", "r") as inputfile:
		for lines in inputfile.readlines():
			words = lines.split()
			dict_key = event_key(words[0], words[1], words[2])
			p_event_voice_calls[dict_key] = float(words[3])
			p_event_meet_devices[dict_key] = float(words[4])
	with open("non_data_event_probability_nodobo.tsv", "r") as inputfile:
		for lines in inputfile.readlines():
			words = lines.split()
			dict_key = event_key(words[0], words[1], words[2])
			p_non_data_meet_data[dict_key] = float(words[3])
	with open("data_user_receive_other_probability_nodobo.tsv", "r") as inputfile:
		for lines in inputfile.readlines():
			words = lines.split()
			dict_key = event_key(words[0], words[1], words[2])
			p_data_receive_other[dict_key] = float(words[3])

def predict_p_voice_call(user, date_time):
	return get_value_from_prediction_map(user, date_time, p_event_voice_calls)

def predict_p_meet_device(user, date_time):
	return get_value_from_prediction_map(user, date_time, p_event_meet_devices)

def predict_p_meet_data(user, date_time):
	return get_value_from_prediction_map(user, date_time, p_non_data_meet_data)

def predict_p_receive_other(user, date_time):
	return get_value_from_prediction_map(user, date_time, p_data_receive_other)

def get_value_from_prediction_map(user, date_time, prediction_map):
	weekday = get_weekday(date_time)
	time_slot = get_time_slot(date_time) + 1 # +1 means we need the possibility of the next timeslot
	key = event_key(user, weekday, time_slot)
	if key in prediction_map.keys():
		return prediction_map[key]
	else:
		return 0.0

def get_weekday(date_time):
	return date_time.weekday()

def get_time_slot(date_time):
	slot = (date_time.hour * 60 + date_time.minute) / TIME_SPLIT
	return slot

if __name__ == '__main__':
	
	#init_device_to_user_dict()
	#init_p_events_mit()
	init_p_events_nodobo()
	init_user_statues()

	print "date total_energy results_not_upload results_force_upload"
	for i in range(7*2):
		#init_user_statues()
		total_energy = 0
		total_energy_data = 0
		solution(TEST_DATE+timedelta(days=i), True)

	with open("records.txt", "w") as outputfile:
		outputfile.write(transfer_records_str)

	with open("bt_user_records.txt", "w") as outputfile:
		outputfile.write(bt_user_transfer_records_str)

	conn.close()